﻿using System.Collections.Immutable;
using System.Linq.Expressions;

namespace zijian666.Data;
public static class EntityExtensions
{

    public static async Task<bool> InsertAsync(this DbConnection connection, object entity, CancellationToken cancellationToken = default)
    {
        if (entity is null)
        {
            return false;
        }
        var entities = new[] { entity };
        var context = await DbInterceptors.OnEntityInserting(() => new(connection, entities, cancellationToken));
        var sqlbuilder = connection.GetSqlBuilder();
        var sqls = sqlbuilder.InsertSql(entities).ToArray();
        var result = await connection._ExecuteNonQueryAsync(null, sqls, cancellationToken) is 1;
        if (context is not null)
        {
            _ = await DbInterceptors.OnEntityInserted(() => context);
        }
        return result;
    }

    public static async Task<int> DeleteAsync(this DbConnection connection, object entity, CancellationToken cancellationToken = default)
    {
        if (entity is null)
        {
            return 0;
        }
        var entities = new[] { entity };
        var context = await DbInterceptors.OnEntityDeleting(() => new(connection, entities, cancellationToken));
        var sqlbuilder = connection.GetSqlBuilder();
        var sqls = sqlbuilder.DeleteSql(entities).ToArray();
        var result = await connection._ExecuteNonQueryAsync(null, sqls, cancellationToken);
        if (context is not null)
        {
            _ = await DbInterceptors.OnEntityDeleted(() => context);
        }
        return result;
    }

    public static async Task<int> UpdateAsync(this DbConnection connection, object entity, CancellationToken cancellationToken = default)
    {
        return await UpdateAsync(connection, entity, (Func<PropertyInfo, bool>?)null, cancellationToken);
    }

    public static async Task<int> UpdateAsync(this DbConnection connection, object entity, IEnumerable<string> names, CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(names);
        return await UpdateAsync(connection, entity, names.Contains, cancellationToken);
    }
    public static Task<int> UpdateAsync(this DbConnection connection, object entity, Func<string, bool>? filter, CancellationToken cancellationToken)
    {
        return UpdateAsync(connection, entity, filter is null ? null : p => filter.Invoke(p.Name), cancellationToken);
    }

    public static async Task<int> UpdateAsync(this DbConnection connection, object entity, Func<PropertyInfo, bool>? filter, CancellationToken cancellationToken = default)
    {
        if (entity is null)
        {
            return 0;
        }

        var entities = new[] { entity };
        var context = await DbInterceptors.OnEntityUpdateing(() => new(connection, entities, cancellationToken) { Filter = filter });
        if (context != null)
        {
            filter = context.Filter;
        }
        var sqlbuilder = connection.GetSqlBuilder();
        var sqls = sqlbuilder.UpdateSql(entities, filter: filter).ToArray();
        var result = await connection._ExecuteNonQueryAsync(null, sqls, cancellationToken);
        if (context is not null)
        {
            _ = await DbInterceptors.OnEntityUpdated(() => context);
        }
        return result;
    }

    public static Task<int> UpdateAsync<T>(this DbConnection connection, Expression<Func<T>> initExpr, CancellationToken cancellationToken = default)
        where T : class
    {
        var members = initExpr.ParseMemberInit(out var entity).ToHashSet();
        return UpdateAsync(connection, entity, members.Contains, cancellationToken);
    }

    public static bool Insert(this DbConnection connection, object entity)
    {
        if (entity is null)
        {
            return false;
        }
        var entities = new[] { entity };
        var context = DbInterceptors.OnEntityInserting(() => new(connection, entities, CancellationToken.None)).GetAwaiter().GetResult();
        var sqlbuilder = connection.GetSqlBuilder();
        var sqls = sqlbuilder.InsertSql(entities).ToArray();
        var result = connection.ExecuteNonQuery(sqls) is 1;
        if (context is not null)
        {
            _ = DbInterceptors.OnEntityInserted(() => context).GetAwaiter().GetResult();
        }
        return result;
    }

    public static int Delete(this DbConnection connection, object entity)
    {
        if (entity is null)
        {
            return 0;
        }
        var entities = new[] { entity };
        var context = DbInterceptors.OnEntityDeleting(() => new(connection, entities, CancellationToken.None)).GetAwaiter().GetResult();
        var sqlbuilder = connection.GetSqlBuilder();
        var sqls = sqlbuilder.DeleteSql(entities).ToArray();
        var reuslt = connection.ExecuteNonQuery(sqls);
        if (context is not null)
        {
            _ = DbInterceptors.OnEntityDeleted(() => context).GetAwaiter().GetResult();
        }
        return reuslt;
    }

    public static int Update(this DbConnection connection, object entity, Func<PropertyInfo, bool>? filter)
    {
        if (entity is null)
        {
            return 0;
        }
        var entities = new[] { entity };
        var context = DbInterceptors.OnEntityUpdateing(() => new(connection, entities, CancellationToken.None) { Filter = filter }).GetAwaiter().GetResult();
        if (context != null)
        {
            filter = context.Filter;
        }
        var sqlbuilder = connection.GetSqlBuilder();
        var sqls = sqlbuilder.UpdateSql(entities, filter: filter).ToArray();
        var result = connection.ExecuteNonQuery(sqls);
        if (context is not null)
        {
            _ = DbInterceptors.OnEntityUpdated(() => context).GetAwaiter().GetResult();
        }
        return result;
    }

    public static int Update<T>(this DbConnection connection, Expression<Func<T>> initExpr, CancellationToken cancellationToken = default)
        where T : class
    {
        var members = initExpr.ParseMemberInit(out var entity).ToHashSet();
        return Update(connection, entity, members.Contains);
    }

    public static int Update(this DbConnection connection, object entity, Func<string, bool> filter)
    {
        return Update(connection, entity, p => filter.Invoke(p.Name));
    }

    public static int Update(this DbConnection connection, object entity, IEnumerable<string> names)
    {
        ArgumentNullException.ThrowIfNull(names);
        return Update(connection, entity, names.Contains);
    }

    public static int Update(this DbConnection connection, object entity)
    {
        return Update(connection, entity, (Func<PropertyInfo, bool>?)null);
    }

    public static async Task<int> InsertsAsync(this DbConnection connection, IEnumerable<object> entities, CancellationToken cancellationToken = default)
    {
        if (entities is null)
        {
            return 0;
        }
        var array = entities.ToArray();
        if (array.Length == 0)
        {
            return 0;
        }
        var context = await DbInterceptors.OnEntityInserting(() => new(connection, array, cancellationToken));
        var sqlbuilder = connection.GetSqlBuilder();
        var sqls = sqlbuilder.InsertSql(array).ToArray();
        var result = await connection._ExecuteNonQueryAsync(null, sqls, cancellationToken);
        if (context is not null)
        {
            _ = await DbInterceptors.OnEntityInserted(() => context);
        }
        return result;
    }

    public static async Task<int> DeletesAsync(this DbConnection connection, IEnumerable<object> entities, CancellationToken cancellationToken = default)
    {
        var array = entities?.ToArray();
        if (array is null or { Length: 0 })
        {
            return 0;
        }
        var context = await DbInterceptors.OnEntityDeleting(() => new(connection, array, cancellationToken));
        var sqlbuilder = connection.GetSqlBuilder();
        var sqls = sqlbuilder.DeleteSql(array).ToArray();
        var result = await connection._ExecuteNonQueryAsync(null, sqls, cancellationToken);
        if (context is not null)
        {
            _ = await DbInterceptors.OnEntityDeleted(() => context);
        }
        return result;
    }

    public static async Task<int> UpdatesAsync(this DbConnection connection, IEnumerable<object> entities, CancellationToken cancellationToken = default)
    {
        var array = entities?.ToArray();
        if (array is null or { Length: 0 })
        {
            return 0;
        }
        var context = await DbInterceptors.OnEntityUpdateing(() => new(connection, array, cancellationToken));
        var sqlbuilder = connection.GetSqlBuilder();
        var sqls = sqlbuilder.UpdateSql(array, filter: context?.Filter).ToArray();
        var result = await connection._ExecuteNonQueryAsync(null, sqls, cancellationToken);
        if (context is not null)
        {
            _ = await DbInterceptors.OnEntityUpdated(() => context);
        }
        return result;
    }

    public static int Inserts(this DbConnection connection, IEnumerable<object> entities)
    {
        var array = entities?.ToArray();
        if (array is null or { Length: 0 })
        {
            return 0;
        }
        var context = DbInterceptors.OnEntityInserting(() => new(connection, array, CancellationToken.None)).GetAwaiter().GetResult();
        var sqlbuilder = connection.GetSqlBuilder();
        var sqls = sqlbuilder.InsertSql(array).ToArray();
        var result = connection.ExecuteNonQuery(sqls);
        if (context is not null)
        {
            _ = DbInterceptors.OnEntityInserted(() => context).GetAwaiter().GetResult();
        }
        return result;
    }

    public static int Deletes(this DbConnection connection, IEnumerable<object> entities)
    {
        var array = entities?.ToArray();
        if (array is null or { Length: 0 })
        {
            return 0;
        }
        var context = DbInterceptors.OnEntityDeleting(() => new(connection, array, CancellationToken.None)).GetAwaiter().GetResult();
        var sqlbuilder = connection.GetSqlBuilder();
        var sqls = sqlbuilder.DeleteSql(array).ToArray();
        var result = connection.ExecuteNonQuery(sqls);
        if (context is not null)
        {
            _ = DbInterceptors.OnEntityDeleted(() => context).GetAwaiter().GetResult();
        }
        return result;
    }

    public static int Updates(this DbConnection connection, IEnumerable<object> entities)
    {
        var array = entities?.ToArray();
        if (array is null or { Length: 0 })
        {
            return 0;
        }
        var context = DbInterceptors.OnEntityUpdateing(() => new(connection, array, CancellationToken.None)).GetAwaiter().GetResult();
        var sqlbuilder = connection.GetSqlBuilder();
        var sqls = sqlbuilder.UpdateSql(array, filter: context?.Filter).ToArray();
        var result = connection.ExecuteNonQuery(sqls);
        if (context is not null)
        {
            _ = DbInterceptors.OnEntityUpdated(() => context).GetAwaiter().GetResult();
        }
        return result;
    }


    //public static async Task<T> GetEntity<T>(this DbConnection connection, object key, CancellationToken cancellationToken = default)
    //{
    //    if (key is null)
    //    {
    //        return default;
    //    }
    //    var sqlbuilder = connection.GetSqlBuilder();
    //    var sqls = sqlbuilder.SelectSql(new[] { key }).ToArray();
    //    var result = await connection._ExecuteReaderAsync<T>(null, sqls, cancellationToken);
    //    if (context is not null)
    //    {
    //        _ = await DbInterceptors.OnEntitySelected(() => context);
    //    }
    //    return result.FirstOrDefault();
    //}

}



public class EntityScheam
{
    private static readonly ConcurrentDictionary<Type, EntityScheam> ENTITY_SCHEAM_CACHE = new();

    [return: NotNullIfNotNull(nameof(type))]
    public static EntityScheam? Get(Type? type) => type is null ? null : ENTITY_SCHEAM_CACHE.GetOrAdd(type, t => new EntityScheam(t));

    public static EntityScheam Get<T>() => Get(typeof(T));

    private EntityScheam(Type type)
    {
        Type = type;
        Properties = type.GetProperties()
            .Where(x => !x.IsDefined(typeof(NotMappedAttribute)))
            .Where(x => x.GetIndexParameters().Length == 0)
            .Where(x => x.CanWrite && x.CanRead)
            .Select(p => new EntityPropertyInfo(p))
            .ToList()
            .AsReadOnly();
        TableAttribute = type.GetCustomAttribute<TableAttribute>();
        TableName = TableAttribute?.Name ?? type.Name;
        Table = new FormatSqlName(TableName);
        Columns = Properties.Select(x => x.Column).ToImmutableArray();
    }

    public Type Type { get; }

    public TableAttribute? TableAttribute { get; }

    public string TableName { get; }

    public FormatSqlName Table { get; }

    public ImmutableArray<FormatSqlName> Columns { get; }

    public FormatSqlName this[string columnName] => new(GetColumnName(columnName));

    public IReadOnlyCollection<EntityPropertyInfo> Properties { get; set; }

    public string GetColumnName(string propertyNameOrColumnName)
    {
        if (string.IsNullOrWhiteSpace(propertyNameOrColumnName))
        {
            throw new ArgumentNullException(nameof(propertyNameOrColumnName));
        }
        var property = Properties.FirstOrDefault(x => x.PropertyInfo.Name == propertyNameOrColumnName || x.ColumnName == propertyNameOrColumnName);
        return property?.ColumnName ?? propertyNameOrColumnName;
    }

}

public class EntityPropertyInfo
{
    public EntityPropertyInfo(PropertyInfo property)
    {
        PropertyInfo = property;
        ColumnAttribute = property.GetCustomAttribute<ColumnAttribute>();
        DescriptionAttribute = property.GetCustomAttribute<DescriptionAttribute>();
        ColumnName = ColumnAttribute?.Name ?? property.Name;
        IsKey = property.IsDefined(typeof(KeyAttribute));
        IsRequired = property.IsDefined(typeof(RequiredAttribute));
        MaxLength = property.GetCustomAttribute<StringLengthAttribute>()?.MaximumLength;
        IsString = property.PropertyType == typeof(string) || property.PropertyType == typeof(char);
        Column = new(ColumnName);
    }

    public PropertyInfo PropertyInfo { get; }

    public ColumnAttribute? ColumnAttribute { get; }

    public DescriptionAttribute? DescriptionAttribute { get; }

    public string ColumnName { get; }
    public FormatSqlName Column { get; }

    public bool IsKey { get; }

    public bool IsRequired { get; }

    public int? MaxLength { get; }

    public bool IsString { get; }

    public object? GetValue(object entity)
    {
        return PropertyInfo.GetValue(entity);
    }

    public FormatValue GetFormatValue(object entity)
    {
        var value = GetValue(entity);

        if (value is IDbDataSerializer serializer)
        {
            value = serializer.Serialize();
        }

        var dbtype = ColumnAttribute?.TypeName;
        return new FormatValue(value, ":" + dbtype);
    }
}